/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *   Author: Tick <ticktock35@gmail.com>
 *
 */


#include "feature.h"
#include <stdlib.h>
#include <string.h>

#include "debug.h"

static inline Feature * __feature_new(Features *FS, const char *feature_name);
static inline Feature_Value * __feature_value_new(const char *value);
static inline void __feature_value_free(Feature_Value *FV);

static Features * __mokoko_features_singlton__ = NULL;

/**
 * @brief Get the singleton of Features
 */
Features * mokoko_features_singlton_get() {
    if (__mokoko_features_singlton__)
        return __mokoko_features_singlton__;
    __mokoko_features_singlton__ = (Features *)calloc(1, sizeof(Features));

    INIT_LIST_HEAD(&__mokoko_features_singlton__->feature_head);
    __mokoko_features_singlton__->length = 0;
    return __mokoko_features_singlton__;
}

/**
 * @brief Search if a feature is exist or not
 * @param key the name of feature going to search
 * @return Null if there not no such feature, if found returns that feature
 */
Feature *mokoko_feature_search(Features *FS, const char *key) {
    Feature * F = NULL;
    struct list_head *ptr, *next;
    if (!FS)
        FS = mokoko_features_singlton_get();
    if (!key)
        return NULL;
    list_for_each_safe(ptr, next, &FS->feature_head) {
        F = list_entry (ptr, Feature, node);
        if (F->name && !strcmp(F->name, key))
            return F;
    }
    return NULL;
}

/**
 * @brief Search if the feature has the value we want
 * @param feature The key value of feature
 * @param value The value we are testing
 * @return 0 if false
 */
int mokoko_feature_has_value(Features *FS, const char *feature, const char *value) {
    Feature * F;
    Feature_Value *FV;

    struct list_head *ptr, *next;
    if (!feature || !value)
        return 0;
    F = mokoko_feature_search(FS, feature);
    if (!F)
        return 0;
    list_for_each_safe(ptr, next, &F->value_head) {
        FV = list_entry(ptr, Feature_Value, node);
        if (FV->value && !strcmp(FV->value, value))
            return 1;
    }
    return 0;
}


/**
 * @brief Add a value to a feature, if that feature does not exists create it.
 * @param feature The feature we want to add value on
 * @param value the value going to add
 * @return true is successful
 */
int mokoko_feature_value_add(Features *FS, const char *feature, const char *value) {
    Feature *F;
    Feature_Value *FV;
    if (!feature) {
        error("Bad parameters!! feature = %p value= %p\n", feature, value);
        return 0;
    }
    F = mokoko_feature_search (FS, feature);
    if (!F) {
        F = __feature_new(FS, feature);
    }
    if (value && !mokoko_feature_has_value(FS, feature, value)) {
        FV = __feature_value_new(value);
        list_add(&FV->node, &F->value_head);
        F->length++;
    }
    return 1;
}


/**
 * @brief remove value from value
 * @param feature The feature going to remove value
 * @param value The value is going to be removed
 * @return fail returns 0
 */
int mokoko_feature_value_del(Features *FS, const char *feature, const char *value) {
    Feature *F;
    Feature_Value *FV;
    struct list_head *ptr, *next;
    if (!feature || !value) {
        error("Bad parameters!! feature = %p value= %p\n", feature, value);
        return 0;
    }
    F = mokoko_feature_search (FS, feature);
    if (!F) {
        error("No such feature!!\n");
        return 0;
    }
    if (!mokoko_feature_has_value(FS, feature, value))
        return 1;
    list_for_each_safe(ptr, next, &F->value_head) {
        FV = list_entry(ptr, Feature_Value, node);
        if (!strcmp(FV->value, value)) {
            F->length--;
            __feature_value_free(FV);
        }
    }
    return 1;
}

/**
 * @brief remove the feature
 * @param feature The feature is going to be removed.
 * @return fail return 0
 */
int mokoko_feature_del(Features *FS, const char *feature) {
    Feature *F;
    Feature_Value *FV;
    struct list_head *ptr, *next;
    if (!FS)
        FS = mokoko_features_singlton_get();
    if (!feature) {
        error("Bad parameters!! feature = %p\n", feature);
        return 0;
    }
    F = mokoko_feature_search (FS, feature);
    if (!F) {
        return 1;
    }
    list_for_each_safe(ptr, next, &F->value_head) {
        FV = list_entry(ptr, Feature_Value, node);
        __feature_value_free(FV);
    }
    FS->length--;
    list_del(&F->node);
    free(F->name);
    free(F);
    return 1;
}

/**
 * @brief find the values of a feature by index
 * @param feature The feaure want to get value
 * @param index the index of the value, if value out of boundary will return NULL
 * @return Null if there are no such value, or return by reference.
 */
char const*mokoko_feature_value_get(Features *FS, const char *feature, const int index) {
    Feature *F =  mokoko_feature_search (FS, feature);
    Feature_Value *FV;
    int i = 0;
    struct list_head *ptr, *next;
    if (!F)
        return NULL;
    if (index < 0)
        return NULL;
    list_for_each_safe(ptr, next, &F->value_head) {
        FV = list_entry(ptr, Feature_Value, node);
        if (i == index)
            return FV->value;
        i++;
    }
    return NULL;
}

/**
 * @brief Get the count of how many features we get
 */
int mokoko_features_count(Features *FS) {
    if (!FS)
        return mokoko_features_singlton_get()->length;
    return FS->length;
}

/**
 * @brief returns the number of feature values of particular feature
 */
int mokoko_feature_value_count(Features *FS, const char *feature) {
    Feature *F;
    F = mokoko_feature_search (FS, feature);
    return F->length;
}


/**
 * @brief Clean up all the features, let the feature scheme back to initial status.
 */
void mokoko_features_cleanup(Features *FS) {
    Feature *F, *Fn;
    Feature_Value *FV, *FVn;
    if (!FS)
        FS = mokoko_features_singlton_get();
    list_for_each_entry_safe (F, Fn, &FS->feature_head, node) {
        list_for_each_entry_safe(FV, FVn, &F->value_head, node) {
            free(FV->value);
            free(FV);
        }
        free(F->name);
        free(F);
    }
    if (FS == __mokoko_features_singlton__)
        __mokoko_features_singlton__ = NULL;
    free(FS);
}

// ============= Internal Function ==============
static inline Feature * __feature_new(Features *fs, const char *name) {
    Feature *F;
    if (!fs)
        fs = mokoko_features_singlton_get();
    F = (Feature *) calloc(1, sizeof(Feature));
    INIT_LIST_HEAD(&F->value_head);
    INIT_LIST_HEAD(&F->node);
    F->length = 0;
    F->name = strdup(name);
    list_add(&F->node, &fs->feature_head);
    fs->length++;
    return F;
}


static inline Feature_Value * __feature_value_new(const char *value) {
    Feature_Value *FV;
    FV = (Feature_Value *)calloc(1, sizeof(Feature_Value));
    INIT_LIST_HEAD(&FV->node);
    FV->value = strdup(value);
    return FV;
}

static inline void __feature_value_free(Feature_Value *FV) {
    list_del(&FV->node);
    free(FV->value);
    free(FV);
}

